import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.IOException;
import java.util.ArrayList;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;
import javax.swing.JFrame;

import com.sun.opengl.util.FPSAnimator;

/**
 * 
 * @author Eric Muehlberg
 *
 *Contains the main loop of the program
 */
public class Engine implements GLEventListener, KeyListener, MouseListener, MouseMotionListener, ActionListener
{
	GLU glu = new GLU();
	GL gl;
	GLCanvas canvas;
	FPSAnimator animator;
	player p;
	timer t;
	Map level;
	Camera camera;
	
	float xpos = 0, ypos = 0, zpos = 0;
	float centerx, centery, centerz;
	float roth = 0, rotv = 0;
	
	float znear = 0.01f; 
	float zfar = 1000.0f;
	
	int mouseX, mouseY, mouseButton;
	int keyButton;
	int wrotationSegments;
	float motionSpeed, wrotateSpeed;
	float animation_speed = 1.0f;
	int winW;
	int winH;
	
	long time;
	boolean quit;
	
	/**
	 * 
	 * @author Eric Muehlberg
	 *
	 *	Handles everything camera related
	 *
	 *	-Needs to be implemented properly
	 *	-Leave for later
	 */
	public class Camera
	{
		float posx ,posy, posz;
		float centerx, centery, centerz;
		float roth, rotv;
		boolean follow;
		WObject followobj;
		
		public Camera()
		{
			centerx = 0;
			centery = 0;
			centerz = 0;
			posx = centerx;
			posy = centery;
			posz = centerz + 500;
			roth = 0;
			rotv = 0;
			follow = false;
			followobj = null;
		}
		
		/**
		 * Snap the camera right to follow the worlds leftward rotation
		 */
		public void snapleft()
		{
			
		}
		
		/**
		 * Snap the camera left to follow the worlds rightward rotation
		 */
		public void snapright()
		{
			
		}
		
		/**
		 * Follow The specified object
		 * @param f
		 * @param w
		 */
		public void setfollow(boolean f, WObject w)
		{
			follow = f;
			if(f)
				followobj = w;
		}
		
		/**
		 * Update the camera settings
		 */
		public void update()
		{
			if(follow)
			{
				/*this.posx = followobj.cube.pos.x;
				this.posy = followobj.cube.pos.y;
				this.posz = followobj.cube.pos.z;*/
			}
		}
	}
	
	public Engine()
	{
		JFrame frame = new JFrame("473 Final");
		t = new timer(); 
		t.start();
		p = new player(new ObjectThree(0,1,0,1,1,1,null,"cube.obj"));
		
		//Init everything
		try {
			level = new Map("test2.level");
		} catch (IOException e) {
			System.out.println("level could not be loaded");
		}
		
		quit = false;
		
		canvas = new GLCanvas();
		canvas.addGLEventListener(this);
		canvas.addMouseListener(this);
		canvas.addMouseMotionListener(this);
		canvas.addKeyListener(this);
		animator = new FPSAnimator(canvas, 30);
		frame.getContentPane().add(canvas);
		frame.setSize(1028,1028);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
		animator.start();
		canvas.requestFocus();
		
		t.start();
		float dy = 0;
		float dx = 0;
		boolean colbot = false;
		
		while(!quit)
		{		
			p.update(t.getTime());
			t.start();
			
			//Check collisions
			for(WObject w: level.mapobjects)
			{
				if(CollisionCheck(p.obj, w))
				{
					CollisionFace coll = PrecisionCheck(p.obj, w);
					
					if(coll.getLeft())
					{
						
					}
					
					if(coll.getRight())
					{
						dx = p.getX() - p.getW();
						p.updateX(dx);
					}
					
					if(coll.getTop())
					{
						
					}
					
					if(coll.getBottom())
					{
						p.updateDY(0);
						colbot = true;
						p.canjump = true;
						p.jumping = false;
						//Bump player to frontmost cube
						if(w.getRZ() > p.getRZ())
						{
							p.updateRZ(w.getRZ());
						}
					}
					
				}
			}
			
			dy = (p.getY() - p.getH());
			p.updateY(dy);
			colbot = false;
			
			//Update display
			canvas.display();	
		}
	}
	
	public static void main(String[] args)
	{
		new Engine();
	}

	public void display(GLAutoDrawable drawable)
	{
		gl = drawable.getGL();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
		gl.glShadeModel(GL.GL_SMOOTH);
		gl.glEnable(GL.GL_CULL_FACE);
		
		gl.glLoadIdentity();
		gl.glTranslatef(-xpos, -ypos, -zpos);
		
		//unnecessary?
		//gl.glTranslatef(centerx, centery, centerz);
		
		//Rotates scene
		gl.glRotatef(360.f - roth, 0, 1.0f, 0);
		
		//Not needed for gameplay but keep for demonstration use
		gl.glRotatef(rotv, 1.0f, 0, 0);

		gl.glTranslatef(-centerx, -centery, -centerz);
		
		for(WObject w: level.mapobjects)
		{	
			gl.glPushMatrix();
			if(((ObjectThree)w).m != null)
			{
				gl.glTranslatef(w.getX(), w.getY(), w.getZ());
			
				((ObjectThree)w).m.Draw(gl, glu);
			}
			gl.glPopMatrix();
		}
		
		gl.glPushMatrix();
		gl.glTranslatef(p.getX(), p.getY(), p.getZ());
		((ObjectThree)p.obj).m.Draw(gl, glu);
		gl.glPopMatrix();
		
	}
	
	void initViewParameters()
	{
		roth = rotv = 0;

		centerx = 0;
		centery = 0;
		centerz = 0;
		xpos = centerx;
		ypos = centery;
		zpos = centerz + 500;
		
		camera = new Camera();

		motionSpeed = 1f;
		
		//World rotation
		wrotateSpeed = 0.01f;
		wrotationSegments = 100;

	}	
	
	public void mousePressed(MouseEvent e) {	
		mouseX = e.getX();
		mouseY = e.getY();
		mouseButton = e.getButton();
		canvas.display();
	}
	
	public void mouseReleased(MouseEvent e) {
		mouseButton = MouseEvent.NOBUTTON;
		canvas.display();
	}	
	
	public void mouseDragged(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		if (mouseButton == MouseEvent.BUTTON3) {
			zpos -= (y - mouseY) * motionSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		} else if (mouseButton == MouseEvent.BUTTON2) {
			xpos -= (x - mouseX) * motionSpeed;
			ypos += (y - mouseY) * motionSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		} else if (mouseButton == MouseEvent.BUTTON1) {
			roth -= (x - mouseX) * wrotateSpeed;
			rotv += (y - mouseY) * wrotateSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		}
	}

	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2)
	{
		// TODO Auto-generated method stub
	}

	//This needs to be changed a bit
	public void init(GLAutoDrawable gld)
	{
		gl = gld.getGL();
		initViewParameters();
		gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
		gl.glClearDepth(1.0f);
		
		 // white light at the eye
	    float light0_position[] = { 0, 0, 1, 0 };
	    float light0_diffuse[] = { 1, 1, 1, 1 };
	    float light0_specular[] = { 1, 1, 1, 1 };
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_POSITION, light0_position, 0);
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_DIFFUSE, light0_diffuse, 0);
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_SPECULAR, light0_specular, 0);
	    
	  //material
	    float mat_ambient[] = { 0, 0, 0, 1 };
	    float mat_specular[] = { .8f, .8f, .8f, 1 };
	    float mat_diffuse[] = { .4f, .4f, .4f, 1 };
	    float mat_shininess[] = { 128 };
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_AMBIENT, mat_ambient, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_SPECULAR, mat_specular, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_DIFFUSE, mat_diffuse, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_SHININESS, mat_shininess, 0);

	    float bmat_ambient[] = { 0, 0, 0, 1 };
	    float bmat_specular[] = { 0, .8f, .8f, 1 };
	    float bmat_diffuse[] = { 0, .4f, .4f, 1 };
	    float bmat_shininess[] = { 128 };
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_AMBIENT, bmat_ambient, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_SPECULAR, bmat_specular, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_DIFFUSE, bmat_diffuse, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_SHININESS, bmat_shininess, 0);

	    float lmodel_ambient[] = { 0, 0, 0, 1 };
	    gl.glLightModelfv( GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient, 0);
	    gl.glLightModeli( GL.GL_LIGHT_MODEL_TWO_SIDE, 1 );

	    gl.glEnable( GL.GL_NORMALIZE );
	    gl.glEnable( GL.GL_LIGHTING );
	    gl.glEnable( GL.GL_LIGHT0 );
	    
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glDepthFunc(GL.GL_LEQUAL);
		//gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
		gl.glCullFace(GL.GL_BACK);
		gl.glEnable(GL.GL_CULL_FACE);
		gl.glShadeModel(GL.GL_SMOOTH);	
	}
	
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
	{
		winW = width;
		winH = height;

		gl.glViewport(0, 0, width, height);
		
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(centerx-10, winW/100, centery-10, winH/100, znear, zfar);
		
		//Perspective related but not needed
		//Keep for potential future use
		//glu.gluPerspective(25.f, (float)width/(float)height, znear, zfar);
		
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();
	}
	
	/**
	 * Does a simple check to see if two objects are colliding.
	 *
	 *	-Need to write a method for more precise check for collision handling
	 *	-Do we want complex collision checking for precision? or just to handle side detection
	 *
	 *	We need at the least, bottom and sides  -nick
	 * 
	 * @param w1
	 * @param w2
	 * @return
	 */
	public boolean CollisionCheck(WObject w1, WObject w2)
	{
		
		if(w1 == null || w2 == null)
		{
			//Print message in case this wasn't supposed to occur
			System.out.println("At least one object null");
			return false;
		}
		else
		{
			//Left of w1 >= right of w2
			if((w1.getRX() - (w1.getRW()/2)) >= (w2.getRX() + (w2.getRW()/2)))
			{
				return false;
			}
			//Right of w1 <= left of w2
			else if((w1.getRX() + (w1.getRW()/2)) <= (w2.getRX() - (w2.getRW()/2)))
			{
				return false;
			}
			//Bottom of w1 >= top of w2
			else if((w1.getY() - (w1.getH()/2)) >= (w2.getY() + (w2.getH()/2)))
			{
				return false;
			}
			//Top of w1 <= top of w2
			else if((w1.getY() + (w1.getH()/2)) <= (w2.getY() - (w2.getH()/2)))
			{
				return false;
			}
			
			//If none of the above apply a collision has occurred
			return true;

		}
	}
	
	/**
	 * Collision check for quads
	 * @param q1
	 * @param q2
	 * @return
	 */
	public boolean CollisionCheckQ(Quad q1, Quad q2)
	{
		
		if(q1 == null || q2 == null)
		{
			//Print message in case this wasn't supposed to occur
			System.out.println("At least one object null");
			return false;
		}
		else
		{
			//Left of w1 >= right of w2
			if((q1.getX() - q1.getDW()) >= (q2.getX() + q2.getDW()))
			{
				return false;
			}
			//Right of w1 <= left of w2
			else if((q1.getX() + q1.getDW()) <= (q2.getX() - q2.getDW()))
			{
				return false;
			}
			//Bottom of w1 >= top of w2
			else if((q1.getY() - q1.getDH()) >= (q2.getY() + q2.getDH()))
			{
				return false;
			}
			//Top of w1 <= top of w2
			else if((q1.getY() + q1.getDH()) <= (q2.getY() - q2.getDH()))
			{
				return false;
			}
			
			//If none of the above apply a collision has occurred
			return true;

		}
	}
	
	/**
	 * Precision Collision Check
	 */
	public CollisionFace PrecisionCheck(WObject w1, WObject w2)
	{
		//Create four quads for each object
		Quad LeftA, LeftB;
		Quad RightA, RightB;
		Quad TopA, TopB;
		Quad BottomA, BottomB;
		
		CollisionFace result = new CollisionFace();
		
		//Used for the dimensions of the quads
		float boxsize = 0.05f;
		
		LeftA = new Quad(w1.getRX() - w1.getRW() + boxsize/2, w1.getY(), boxsize, w1.getH() - boxsize);
		LeftB = new Quad(w2.getRX() - w2.getRW() + boxsize/2, w2.getY(), boxsize, w2.getH() - boxsize);
		
		RightA = new Quad(w1.getRX() + w1.getRW() - boxsize/2, w1.getY(), boxsize, w1.getH() - boxsize);
		RightB = new Quad(w2.getRX() + w2.getRW() - boxsize/2, w2.getY(), boxsize, w2.getH() - boxsize);
		
		TopA = new Quad(w1.getRX(), w1.getY() + w1.getH() - boxsize/2, w1.getRW() - boxsize, boxsize);
		TopB = new Quad(w2.getRX(), w2.getY() + w2.getH() - boxsize/2, w2.getRW() - boxsize, boxsize);		

		BottomA = new Quad(w1.getRX(), w1.getY() - w1.getH() + boxsize/2, w1.getRW() - boxsize, boxsize);
		BottomB = new Quad(w2.getRX(), w2.getY() - w2.getH() + boxsize/2, w2.getRW() - boxsize, boxsize);
		
		//TopA collision BottomB
			//setTop
		if(CollisionCheckQ(TopA, BottomB))
			result.setTop(true);
		
		//TopB collision BottomA
			//setBottom
		if(CollisionCheckQ(TopB, BottomA))
			result.setBottom(true);
		
		//LeftA collision RightB
			//setLeft
		if(CollisionCheckQ(LeftA, RightB))
			result.setLeft(true);
		
		//RightA collision LeftB
			//setRight
		if(CollisionCheckQ(RightA, LeftB))
			result.setRight(true);
		
		return result;
	}
	
	//rotates the image to the left 90 degrees
	private void rotateRight()
	{
		int delayTime = 3;
		stopProjection();
		for(int i = 0; i < wrotationSegments; i++)
		{
			roth += 90*wrotateSpeed;
			canvas.display();
			try {
				Thread.sleep(delayTime);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

	//rotates the image to the left 90 degrees
	private void rotateLeft()
	{
		int delayTime = 3;
		stopProjection();
		for(int i = 0; i < wrotationSegments; i++)
		{
			roth -= 90*wrotateSpeed;
			canvas.display();
			try {
				Thread.sleep(delayTime);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	//stops the 2d projections of the 3d screen while the screen rotates
	private void stopProjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * Handles perspective shifts to the right
	 */
	public void snapRight()
	{
		p.snapRight();
		for(WObject w: level.mapobjects)
		{
			((Cube)w).snapright();
		}
		rotateRight();
		for(WObject w: level.mapobjects)
		{
			//Smart collision check to see if any object is in in the same xy coordinates
			if(CollisionCheck(w, p.obj))
			{
				CollisionFace coll = PrecisionCheck(w, p.obj);
				if(coll.inside() >= 3)
				{
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//Assume that you will never get stuck in an infinite loop
					snapLeft();
					break;
				}
			}
			
		}
	}
	
	/**
	 * handles perspective shifts to the right
	 */
	public void snapLeft()
	{
		p.snapLeft();
		for(WObject w: level.mapobjects)
		{
			((Cube)w).snapleft();
		}
		rotateLeft();
		for(WObject w: level.mapobjects)
		{
			//Smart collision check to see if any object is in in the same xy coordinates
			if(CollisionCheck(w, p.obj))
			{
				CollisionFace coll = PrecisionCheck(w, p.obj);
				if(coll.inside() >= 3)
				{
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//Assume that you will never get stuck in an infinite loop
					snapRight();
					break;
				}
			}
			
		}
	}
	
	@Override
	public void keyTyped(KeyEvent e)
	{

		switch (keyButton)
		{
			case KeyEvent.VK_Q:
				snapLeft();
				break;
			case KeyEvent.VK_E:
				snapRight();
				break;
			case KeyEvent.VK_W:
				p.Jump();
				break;
		}
	//	p.update();
	}



	@Override
	public void keyPressed(KeyEvent e)
	{
		keyButton = e.getKeyCode();
		//canvas.display();

		switch (keyButton)
		{
			case KeyEvent.VK_A:
				p.moveleft = true;
				
				break;
			case KeyEvent.VK_D:
				p.moveright = true;
				
				break;
			case KeyEvent.VK_F:
				camera.setfollow(!camera.follow, p.obj);
				break;
		}
//		smartCollisionCheck(level.mapobjects);
	//	p.update();
		
	
	}

	@Override
	public void keyReleased(KeyEvent e)
	{
		keyButton = e.getKeyCode();
		//canvas.display();

		switch (keyButton)
		{
			case KeyEvent.VK_A:
				p.moveleft = false;
				
				break;
			case KeyEvent.VK_D:
				p.moveright = false;
				
				break;
		}

	//	p.update();
		
		keyButton = KeyEvent.VK_UNDEFINED;
	//	canvas.display();
	}
	
	

}
